#include "lexical.h"

lexical::invalidToken::invalidToken(unsigned ln) throw():
	line(ln){}
	
const char *lexical::invalidToken::what() const throw(){
	
	std::ostringstream osstream;

	osstream << "An invalid character at line: ";
	osstream << line << std::endl;
	return osstream.str().c_str();

}

inline unsigned lexical::invalidToken::getLine() const{

	return line;

}

lexical::lexical(reader *rd): 
	fileReader(rd), line(1){}

inline std::string &lexical::getString(std::string &str, unsigned num){

	return fileReader->getString(str, num);

}

bool lexical::compare(const char *str, unsigned len, unsigned *j){

	unsigned i;
	
    for(i = 0; i < len; i++){
        *j++;
        if(str[i] != getCharacter()){
			characterBack();
			return false;
		}
    }
	return true;

}

int lexical::getCharacter() throw(std::ifstream::failure){

	int c = fileReader->getChar();
	
	if(c == CHARACTER_LF)
		line++;
	return c;

}

void lexical::characterBack() throw(reader::bufferOverflow){

	if(fileReader->getCurrentChar() == '\n')
		line--;
	fileReader->getBack();

}

lexical::token &lexical::getRealNumber(token &tok) 
	throw(reader::bufferOverflow, std::ifstream::failure){

	enum states{SIGN, DOT, NUMBER};
	
	states state = NUMBER;
	char c = getCharacter();
	unsigned counter = 0;
	
	if(c == '-'){
		counter++;
		c = getCharacter();
		state = SIGN;
	}
	while(isdigit(c)){
		counter++;
		state = NUMBER;
		c = getCharacter();
		if(c == '.' && state == NUMBER){
			state = DOT;
			counter++;
			c = getCharacter();
		}
	}
	if(counter > 0 && state != SIGN)
		getString(tok.lexem, counter);
	else
		tok.symbol = token::ERR;
	characterBack();
	return tok;

}

lexical::token &lexical::getIntegerNumber(token &tok)
	throw(reader::bufferOverflow, std::ifstream::failure){

	enum states{NUMBER, DOT};
	
	states state = NUMBER;
	char c = getCharacter();
	unsigned counter = 0;
	
	while(isdigit(c)){
		counter++;
		c = getCharacter();
		if(c == '.' && state == NUMBER){
			state = DOT;
			counter++;
			c = getCharacter();
		}
	}
	if(counter > 0 && state == NUMBER)
		getString(tok.lexem, counter);
	else
		tok.symbol = token::ERR;
	characterBack();
	return tok;

}

lexical::token &lexical::getName(token &tok) throw(reader::bufferOverflow, std::ifstream::failure){

	char c;
	unsigned i = 0;
	
	do{
		c = getCharacter();
	}while(isspace(c));
	
	if(isalpha(c)){
		do{
			c = getCharacter();
			i++;
		}while(isalnum(c) || c == '_');
		getString(tok.lexem, i);
	}else
		tok.symbol = token::ERR;
	characterBack();
	return tok;

}

lexical::token &lexical::parseComment(token &tok) throw(reader::bufferOverflow, std::ifstream::failure){

	char c;
	unsigned i = 0;
	
	do{
        i++;
		c = getCharacter();
	}while(isspace(c));
	
	if(c == '#'){	
		if(getCharacter() == '!')
			tok.symbol = token::OBJECT_END;
		else{
			tok.symbol = token::OBJECT_START;
			characterBack();
		}
        if(compare(RESERVE_OBJECT, RESERVE_OBJECT_LEN, &i)){
			if(getCharacter() == ' '){
				getName(tok);
			}
			else{
				tok.symbol = token::ERR;
				characterBack();
			}
		}
		else
			tok.symbol = token::ERR;
	}
    while(c != ')' && c != std::ios::eofbit && tok.symbol != token::ERR){
		i++;
		c = getCharacter();
	}
	tok.endOffset = getLength();
	if(tok.symbol == token::COMMENT && i > 0)
		getString(tok.lexem, i);
	return tok;
	
}

lexical::token &lexical::parseAlterComment(token &tok) throw(reader::bufferOverflow, std::ifstream::failure){

    char c;
    unsigned i = 0;

    do{
        i++;
        c = getCharacter();
    }while(isspace(c));

    if(c == RESERVE_ALTER_OBJECT_START[0]){
        characterBack();
        if(compare(RESERVE_ALTER_OBJECT_START, RESERVE_ALTER_OBJECT_START_LEN, &i)){
            tok.symbol = token::OBJECT_ALTER_START;
            getName(tok);
        }
    }
    else if(c == RESERVE_ALTER_OBJECT_END[0]){
        characterBack();
        if(compare(RESERVE_ALTER_OBJECT_END, RESERVE_ALTER_OBJECT_END_LEN, &i)){
            tok.symbol = token::OBJECT_ALTER_END;
            getName(tok);
        }
    }
    while('\n' != c && c != std::ios::eofbit){
        i++;
        c = getCharacter();
    }
    tok.endOffset = getLength();
    if(tok.symbol == token::COMMENT && i > 0)
        getString(tok.lexem, i);
    return tok;

}

inline unsigned long lexical::getLength() const{

	return fileReader->getLength();

}

lexical::token &lexical::getToken(token &tok)
	throw(invalidToken, reader::bufferOverflow, std::ifstream::failure){
	
	char c;
	
	do{
		c = getCharacter();
    }while(isspace(c) || c == '\0');
	
	tok.startOffset = getLength() - 1;
	c = toupper(c);
	tok.line = line;
	switch(c){
		case 'G':
			c = getCharacter();
			characterBack();
			if(isdigit(c)){
				tok.symbol = token::GO_G;
				getIntegerNumber(tok);
			}
			break;
		case 'F':
			tok.symbol = token::FEED_F;
			getRealNumber(tok);
			break;
		case 'S':
			tok.symbol = token::SPINDLE_S;
			getIntegerNumber(tok);
			break;
		case 'M':
			c = getCharacter();
			characterBack();
			if(isdigit(c)){
				tok.symbol = token::MACHINE_M;
				getIntegerNumber(tok);
			}
			break;
		case 'N':
			tok.symbol = token::LINE_N;
			getIntegerNumber(tok);
			break;
		case 'R':
			tok.symbol = token::RADIUS_R;
			getRealNumber(tok);
			break;
		case 'X':
			tok.symbol = token::AXIS_X;
			getRealNumber(tok);
			break;
		case 'Y':
			tok.symbol = token::AXIS_Y;
			getRealNumber(tok);
			break;
		case 'Z':
			tok.symbol = token::AXIS_Z;
			getRealNumber(tok);
			break;
		case 'I':
			tok.symbol = token::ARC_I;
			getRealNumber(tok);
			break;
		case 'K':
			tok.symbol = token::ARC_K;
			getRealNumber(tok);
			break;
		case 'J':
			tok.symbol = token::ARC_J;
			getRealNumber(tok);
			break;
		case '(':
			tok.symbol = token::COMMENT;
			parseComment(tok);
			break;
        case ';':
            tok.symbol = token::COMMENT;
            parseAlterComment(tok);
            break;
		case 'T':
			tok.symbol = token::TOOL_T;
			getIntegerNumber(tok);
			break;
		case 'O':
			tok.symbol = token::SUBROUTINE_O;
			getIntegerNumber(tok);
			break;
		case 'P':
			tok.symbol = token::CALL_P;
			getIntegerNumber(tok);
			break;
		case std::ios::eofbit:
			tok.symbol = token::END;
			break;
		default:
			tok.symbol = token::ERR;
	}
    if(tok.symbol == token::ERR){
        std::cout << (int) c << std::endl;
        throw invalidToken(tok.line);
    }
	return tok;

}
